package org.mx.service.client.rest;

import java.util.Map;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.HttpsURLConnection;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;

import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 描述： 基于Jetty的Restful服务的调用客户端工具类
 *
 * @author john peng
 * Date time 2017/11/4 4:08 PM
 */
public class RestClientInvoke {
	private static final Logger logger = LoggerFactory.getLogger(RestClientInvoke.class);
	private static final long DEFAULT_CONNECT_TIMEOUT = 3000, DEFAULT_READ_TIMEOUT = 5000;

	private SslContextFactory sslContextFactory;
	private Client client;
	private long connectTimeout = DEFAULT_CONNECT_TIMEOUT, readTimeout = DEFAULT_READ_TIMEOUT;

	/**
	 * 构造函数
	 */
	public RestClientInvoke() {
		this(DEFAULT_CONNECT_TIMEOUT, DEFAULT_READ_TIMEOUT);
	}

	/**
	 * 构造函数
	 *
	 * @param connectTimeout 连接超时值，单位为毫秒
	 * @param readTimeout    读取超时值，单位为毫秒
	 */
	public RestClientInvoke(long connectTimeout, long readTimeout) {
		super();
		setTimeout(connectTimeout, readTimeout);
		buildClient();
	}

	/**
	 * 构造函数
	 *
	 * @param keystorePath       keystore文件路径
	 * @param keystoreType		 keystore类型
	 * @param keystorePassword   keystore密码，支持OBF加密
	 * @param keyManagerPassword keystore管理密码，支持OBF加密
	 */
	public RestClientInvoke(String keystorePath, String keystoreType, String keystorePassword,
			String keyManagerPassword) {
		this(keystorePath, keystoreType, keystorePassword, keyManagerPassword, true, DEFAULT_CONNECT_TIMEOUT,
				DEFAULT_READ_TIMEOUT);
	}

	/**
	 * 构造函数
	 *
	 * @param keystorePath         keystore文件路径
	 * @param keystoreType		   keystore类型
	 * @param keystorePassword     keystore密码，支持OBF加密
	 * @param keyManagerPassword   keystore管理密码，支持OBF加密
	 * @param ignoreHostnameVerify 设置为true则忽略主机名验证
	 */
	public RestClientInvoke(String keystorePath, String keystoreType, String keystorePassword,
			String keyManagerPassword, boolean ignoreHostnameVerify) {
		this(keystorePath, keystorePassword, keystoreType, keyManagerPassword, ignoreHostnameVerify,
				DEFAULT_CONNECT_TIMEOUT, DEFAULT_READ_TIMEOUT);
	}

	/**
	 * 构造函数
	 *
	 * @param keystorePath         keystore文件路径
	 * @param keystoreType		   keystore类型
	 * @param keystorePassword     keystore密码，支持OBF加密
	 * @param keyManagerPassword   keystore管理密码，支持OBF加密
	 * @param ignoreHostnameVerify 设置为true则忽略主机名验证
	 * @param connectTimeout       连接超时值，单位为毫秒
	 * @param readTimeout          读取超时值，单位为毫秒
	 */
	public RestClientInvoke(String keystorePath, String keystoreType, String keystorePassword,
			String keyManagerPassword, boolean ignoreHostnameVerify, long connectTimeout, long readTimeout) {
		super();
		buildSslClient(keystorePath, keystoreType, keystorePassword, keyManagerPassword, ignoreHostnameVerify);
	}

	/**
	 * 创建一个常规的HTTP调用客户端
	 */
	private void buildClient() {
		ClientBuilder builder = ClientBuilder.newBuilder();
		builder.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS);
		builder.readTimeout(readTimeout, TimeUnit.MILLISECONDS);
		client = builder.build();
	}

	/**
	 * 创建一个SSL隧道的HTTP调用客户端
	 *
	 * @param keystorePath         keystore文件路径
	 * @param keystoreType		   keystore类型
	 * @param keystorePassword     keystore密码，支持OBF加密
	 * @param keyManagerPassword   keystore管理密码，支持OBF加密
	 * @param ignoreHostnameVerify 设置为true则忽略主机名验证
	 */
	private void buildSslClient(String keystorePath, String keystoreType, String keystorePassword,
			String keyManagerPassword, boolean ignoreHostnameVerify) {
		try {
			if (ignoreHostnameVerify) {
				HttpsURLConnection.setDefaultHostnameVerifier((s, sslSession) -> true);
			}
			ClientBuilder builder = ClientBuilder.newBuilder();
			sslContextFactory = new SslContextFactory.Client(true);
			sslContextFactory.setKeyStorePath(keystorePath);
			sslContextFactory.setKeyStoreType(keystoreType);
			sslContextFactory.setKeyStorePassword(keystorePassword);
			sslContextFactory.setKeyManagerPassword(keyManagerPassword);
			sslContextFactory.start();
			builder.sslContext(sslContextFactory.getSslContext());
			builder.connectTimeout(connectTimeout, TimeUnit.SECONDS);
			builder.readTimeout(readTimeout, TimeUnit.SECONDS);
			client = builder.build();
		} catch (Exception ex) {
			if (logger.isErrorEnabled()) {
				logger.error(String.format("Start the SSL context factory fail, keystore path: %s.", keystorePath));
			}
		}
	}

	/**
	 * 设置连接和读取结果的超时，单位为：毫秒。
	 *
	 * @param connectTimeout 连接超时值
	 * @param readTimeout    读取超时值
	 */
	private void setTimeout(long connectTimeout, long readTimeout) {
		if (connectTimeout > 0) {
			this.connectTimeout = connectTimeout;
		}
		if (readTimeout > 0) {
			this.readTimeout = readTimeout;
		}
		if (connectTimeout <= 0 || readTimeout <= 0) {
			if (logger.isWarnEnabled()) {
				logger.warn(String.format(
						"The connect timeout[%d ms] or read timeout[%d ms] is less than 0, it be ignored.",
						connectTimeout, readTimeout));
			}
		} else {
			if (logger.isDebugEnabled()) {
				logger.debug(
						String.format("The connect timeout: %d ms, read timeout: %d ms.", connectTimeout, readTimeout));
			}
		}
	}

	/**
	 * 关闭并释放调用资源
	 */
	public void close() {
		if (client != null) {
			client.close();
			client = null;
		}
		if (sslContextFactory != null) {
			try {
				sslContextFactory.stop();
			} catch (Exception ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Stop the SSL context factory fail.");
				}
			}
			sslContextFactory = null;
		}
	}

	/**
	 * 处理HTTP响应
	 *
	 * @param response   HTTP响应对象
	 * @param resultType 返回结果数据对象类型
	 * @param <R>        返回结果数据对象泛型
	 * @return 返回结果数据对象
	 * @throws RestInvokeException 操作过程中发生的异常
	 */
	private <R> R dowithResponse(Response response, Class<R> resultType) throws RestInvokeException {
		R r = null;
		String error = null;
		if (response.getStatus() == 200) {
			r = response.readEntity(resultType);
		} else {
			error = String.format("error code: %d.", response.getStatus());
		}
		response.close();
		if (r != null) {
			if (logger.isDebugEnabled()) {
				logger.debug("Invoke RESTful service success.");
			}
			return r;
		} else {
			if (logger.isErrorEnabled()) {
				logger.error(String.format("Invoke RESTful service fail, %s.", error));
			}
			throw new RestInvokeException(error);
		}
	}

	/**
	 * 发起一次GET调用
	 *
	 * @param url        URL
	 * @param resultType 定义的结果数据类型
	 * @param <R>        GET返回数据对象泛型
	 * @return GET调用成功后返回的数据对象
	 * @throws RestInvokeException 调用过程中发生的异常
	 * @see #get(String, Class, Map)
	 */
	public <R> R get(String url, Class<R> resultType) throws RestInvokeException {
		return get(url, resultType, null);
	}

	/**
	 * 发起一次GET调用
	 *
	 * @param url        URL
	 * @param resultType 定义的结果数据类型
	 * @param headers    GET操作跟随的HTTP头数据
	 * @param <R>        GET返回数据对象泛型
	 * @return GET调用成功后返回的数据对象
	 * @throws RestInvokeException 调用过程中发生的异常
	 */
	public <R> R get(String url, Class<R> resultType, Map<String, Object> headers) throws RestInvokeException {
		if (logger.isDebugEnabled()) {
			logger.debug(String.format("Request invoke GET RESTful service, url: %s, result type: %s.", url,
					resultType.getName()));
		}
		WebTarget target = client.target(url);
		Invocation.Builder builder = target.request();
		if (headers != null && !headers.isEmpty()) {
			headers.forEach(builder::header);
		}
		Response response = builder.get();
		return dowithResponse(response, resultType);
	}

	/**
	 * 发起一次DELETE调用
	 *
	 * @param url        URL
	 * @param resultType 定义的结果数据类型
	 * @param <R>        DELETE返回数据对象泛型
	 * @return DELETE调用成功后返回的数据对象
	 * @throws RestInvokeException 调用过程中发生的异常
	 * @see #delete(String, Class, Map)
	 */
	public <R> R delete(String url, Class<R> resultType) throws RestInvokeException {
		return delete(url, resultType, null);
	}

	/**
	 * 发起一次DELETE调用
	 *
	 * @param url        URL
	 * @param resultType 定义的结果数据类型
	 * @param headers    DELETE操作跟随的HTTP头数据
	 * @param <R>        DELETE返回数据对象泛型
	 * @return DELETE调用成功后返回的数据对象
	 * @throws RestInvokeException 调用过程中发生的异常
	 */
	public <R> R delete(String url, Class<R> resultType, Map<String, Object> headers) throws RestInvokeException {
		if (logger.isDebugEnabled()) {
			logger.debug(String.format("Request invoke DELETE RESTful service, url: %s, result type: %s.", url,
					resultType.getName()));
		}
		WebTarget target = client.target(url);
		Invocation.Builder builder = target.request();
		if (headers != null && !headers.isEmpty()) {
			headers.forEach(builder::header);
		}
		Response response = builder.delete();
		return dowithResponse(response, resultType);
	}

	/**
	 * 发起一次POST调用
	 *
	 * @param url        URL
	 * @param d          POST传输的数据对象
	 * @param resultType 定义的结果数据类型
	 * @param <R>        POST返回数据对象泛型
	 * @param <D>        POST传输数据对象泛型
	 * @return POST调用成功后返回的数据对象
	 * @throws RestInvokeException 调用过程中发生的异常
	 * @see #post(String, Object, Class, Map)
	 */
	public <R, D> R post(String url, D d, Class<R> resultType) throws RestInvokeException {
		return post(url, d, resultType, null);
	}

	/**
	 * 发起一次POST调用
	 *
	 * @param url        URL
	 * @param d          POST传输的数据对象
	 * @param resultType 定义的结果数据类型
	 * @param headers    POST操作跟随的HTTP头数据
	 * @param <R>        POST返回数据对象泛型
	 * @param <D>        POST传输数据对象泛型
	 * @return POST调用成功后返回的数据对象
	 * @throws RestInvokeException 调用过程中发生的异常
	 */
	public <R, D> R post(String url, D d, Class<R> resultType, Map<String, Object> headers) throws RestInvokeException {
		if (logger.isDebugEnabled()) {
			logger.debug(String.format("Request invoke POST RESTful service, url: %s, result type: %s.", url,
					resultType.getName()));
		}
		WebTarget target = client.target(url);
		Entity<D> entity = null;
		if (d != null) {
			entity = Entity.json(d);
		}
		Invocation.Builder builder = target.request();
		if (headers != null && !headers.isEmpty()) {
			headers.forEach(builder::header);
		}
		Response response = builder.post(entity);
		return dowithResponse(response, resultType);
	}

	/**
	 * 发起一次PUT调用
	 *
	 * @param url        URL
	 * @param d          PUT传输的数据对象
	 * @param resultType 定义的结果数据类型
	 * @param <R>        PUT返回数据对象泛型
	 * @param <D>        PUT传输数据对象泛型
	 * @return PUT调用成功后返回的数据对象
	 * @throws RestInvokeException 调用过程中发生的异常
	 * @see #put(String, Object, Class, Map)
	 */
	public <R, D> R put(String url, D d, Class<R> resultType) throws RestInvokeException {
		return put(url, d, resultType, null);
	}

	/**
	 * 发起一次PUT调用
	 *
	 * @param url        URL
	 * @param d          PUT传输的数据对象
	 * @param resultType 定义的结果数据类型
	 * @param headers    PUT操作跟随的HTTP头数据
	 * @param <R>        PUT返回数据对象泛型
	 * @param <D>        PUT传输数据对象泛型
	 * @return PUT调用成功后返回的数据对象
	 * @throws RestInvokeException 调用过程中发生的异常
	 */
	public <R, D> R put(String url, D d, Class<R> resultType, Map<String, Object> headers) throws RestInvokeException {
		if (logger.isDebugEnabled()) {
			logger.debug(String.format("Request invoke PUT RESTful service, url: %s, result type: %s.", url,
					resultType.getName()));
		}
		WebTarget target = client.target(url);
		Entity<D> entity = null;
		if (d != null) {
			entity = Entity.json(d);
		}
		Invocation.Builder builder = target.request();
		if (headers != null && !headers.isEmpty()) {
			headers.forEach(builder::header);
		}
		Response response = builder.put(entity);
		return dowithResponse(response, resultType);
	}
}
